package com.binge.testThread;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * ClassName:DuplicateClassFinder1
 * Package:com.binge.testThread
 * Description:
 *
 * @Author @Bin
 * @Date 2024/4/1-11:29
 * @Version: v1.0
 */
@Slf4j
public class DuplicateClassFinder1 {


    private static HashMap<String, AtomicInteger> classCount = new HashMap<>();

    private static String path = "/Users/bin/IdeaProjects/basic-management/target/basic-management.jar";

    public static void main(String[] args) {
        Map<String, Set<Desc>> classLocations = new HashMap<>();

        try {
            processFile(Paths.get(path), classLocations, "");
            printDuplicates(classLocations);
        } catch (IOException | NoSuchAlgorithmException e) {
            log.error("Error processing file: " + e.getMessage());
            e.printStackTrace();
        }
    }

    private static void processFile(Path filePath, Map<String, Set<Desc>> classLocations, String parentPath) throws IOException, NoSuchAlgorithmException {
        try (JarFile jarFile = new JarFile(filePath.toFile())) {
            Enumeration<JarEntry> entries = jarFile.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().endsWith(".class")) {
                    //Path tempFile = Files.createTempFile("class", ".class");
                    //JarEntry中 抽出文件读取
                    Desc desc = new Desc();
                    try (InputStream is = jarFile.getInputStream(entry)) {
                        //Files.copy(is, tempFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
                        String classMD5 = calculateMD5(is);
                        desc.setMd5(classMD5);
                    }
                    String className = entry.getName().replace('/', '.').replace(".class", "");

                    //Files.deleteIfExists(tempFile); // Delete the temporary file
                    desc.setName(className);
                    desc.setLocation(parentPath);
                    classLocations.computeIfAbsent(className, k -> new HashSet<>()).add(desc);

                } else if (!entry.isDirectory() && (entry.getName().endsWith(".jar") || entry.getName().endsWith(".war"))) {
                    //获取到jar包名称,"/切割获取到jar包名称"

                    String[] split = entry.getName().split("/");
                    String jarName = split[split.length - 1];
                    Path tempFile = Files.createTempFile(jarName, ".jar");
                    try (InputStream is = jarFile.getInputStream(entry)) {
                        Files.copy(is, tempFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
                        //String nestedJarPath = parentPath.isEmpty() ? filePath.toString() : parentPath + "!" + entry.getName();
                        processFile(tempFile, classLocations, jarName);
                    } finally {
                        Files.deleteIfExists(tempFile); // Ensure the temporary file is deleted
                    }
                }
            }
        }
    }


    private static String calculateMD5(InputStream is) throws IOException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] buffer = new byte[8192];
        int read;
        while ((read = is.read(buffer)) > 0) {
            md.update(buffer, 0, read);
        }
        byte[] md5Bytes = md.digest();
        StringBuilder sb = new StringBuilder();
        for (byte b : md5Bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    private static String calculateFileMD5(Path path) throws IOException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        try (InputStream is = Files.newInputStream(path)) {
            byte[] buffer = new byte[8192];
            int read;
            while ((read = is.read(buffer)) > 0) {
                md.update(buffer, 0, read);
            }
        }
        byte[] md5Bytes = md.digest();
        StringBuilder sb = new StringBuilder();
        for (byte b : md5Bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    //private static void printDuplicates(Map<String, Set<Desc>> classLocations) {
    //    int i = 0;
    //    boolean duplicatesFound = false;
    //    for (Map.Entry<String, Set<Desc>> entry : classLocations.entrySet()) {
    //        if (entry.getValue().size() > 1) {
    //            ++i;
    //            duplicatesFound = true;
    //            log.info("重复类发现: " + entry.getKey());
    //            log.info("发现位置:");
    //            //取出set中的desc的每个location,通过"-"拼接在一起;
    //            String locationsJoined = entry.getValue().stream()
    //                    .map(Desc::getLocation)
    //                    .collect(Collectors.joining("&&"));
    //            classCount.computeIfAbsent(locationsJoined, k -> new AtomicInteger(0)).incrementAndGet();
    //
    //            entry.getValue().forEach(location -> log.info(" - " + location) );
    //            log.info("");
    //        }
    //    }
    //
    //    log.info("共有重复类: {}", i);
    //    List<Map.Entry<String, AtomicInteger>> sortedEntries = classCount.entrySet()
    //            .stream()
    //            .sorted((e1, e2) -> e2.getValue().get() - e1.getValue().get())
    //            .collect(Collectors.toList());
    //    // 打印排序后的结果
    //    sortedEntries.forEach(entry ->
    //            System.out.println(entry.getKey() + ": " + entry.getValue().get())
    //    );
    //    //log.info("重复类位置: {}", classCount);
    //    if (!duplicatesFound) {
    //       log.info("No duplicate classes found.");
    //    }
    //}



    private static void printDuplicates(Map<String, Set<Desc>> classLocations) {
        try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(Paths.get(path).getParent().resolve("duplicate-classes.txt")))) {
            int i = 0;
            boolean duplicatesFound = false;
            for (Map.Entry<String, Set<Desc>> entry : classLocations.entrySet()) {
                if (entry.getValue().size() > 1) {
                    ++i;
                    duplicatesFound = true;
                    out.println("重复类发现: " + entry.getKey());
                    out.println("发现位置:");
                    String locationsJoined = entry.getValue().stream()
                            .map(Desc::getLocation)
                            .collect(Collectors.joining("&&"));
                    classCount.computeIfAbsent(locationsJoined, k -> new AtomicInteger(0)).incrementAndGet();

                    entry.getValue().forEach(location -> out.println(" - " + location));
                    out.println("");
                }
            }

            out.println("共有重复类: " + i);
            List<Map.Entry<String, AtomicInteger>> sortedEntries = classCount.entrySet()
                    .stream()
                    .sorted((e1, e2) -> e2.getValue().get() - e1.getValue().get())
                    .collect(Collectors.toList());

            sortedEntries.forEach(entry ->
                    out.println(entry.getKey() + ": " + entry.getValue().get())
            );

            if (!duplicatesFound) {
                out.println("No duplicate classes found.");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static class Desc {


        private String name;
        private String md5;

        private String location;

        public String getLocation() {
            return location;
        }

        public void setLocation(String location) {
            this.location = location;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getMd5() {
            return md5;
        }

        public void setMd5(String md5) {
            this.md5 = md5;
        }

        @Override
        public String toString() {
            return "Desc{" +
                    "name='" + name + '\'' +
                    ", md5='" + md5 + '\'' +
                    ", location='" + location + '\'' +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            Desc desc = (Desc) o;
            return Objects.equals(name, desc.name) && Objects.equals(md5, desc.md5);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, md5);
        }
    }
}